#include <Arduino.h>
#include <SoftwareSerial.h>  // 软串口库（用于ZW101通信）

//pass配置
#include <WiFiClient.h>
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <ESP8266WebServer.h>
#include "Flash.h"

//显示模块配置
#include <U8g2lib.h>
#include <Wire.h>
U8G2_SSD1306_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);

// ===================== ZW101指纹模块配置 =====================
#define HLK_DEBUG //打开日志打印
// 定义指纹模组的UART引脚
#define FINGERPRINT_RX 15  // GPIO4（ZW101 RX ← ESP TX）
#define FINGERPRINT_TX 13  // GPIO5（ZW101 TX → ESP RX）
SoftwareSerial fingerprintSerial(FINGERPRINT_RX, FINGERPRINT_TX);  // 软串口：(接收脚, 发送脚)

//pass配置
#define VERSION 1
//0-->opaque shell application version with external button & light
//1-->transparent shell application version with one external button
//2-->mini application version without any external equipment
#if VERSION == 0
#define BUTTON  14
#define LIGHT_1  12 //power indicator light
#define LIGHT_2  13 //info indicator light
#elif VERSION == 1
#define BUTTON  0
#endif

#define fingerCtrl 16
#define OLEDCtrl 14
#define touchCtrl 12


SYS_INFO sys_info;
PWD_INFO pwd_info;

const int led = LED_BUILTIN;
ESP8266WebServer server(80);

const String html_head = "<!DOCTYPE ><html ><head><title>密码库</title><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" /><meta name=\"viewport\" content=\"width=device-width, minimum-scale=1.0, maximum-scale=1.0\">";
const String html_css_1 = "<style>#myInput {background-image: url(\"\");background-position: 10px 12px; background-repeat: no-repeat; width: 100%;font-size: 16px;padding: 12px 20px 12px 40px;border: 1px solid #ddd;margin-bottom: 12px; }h2 {text-align: center;}";
const String html_css_2 = "table {table-layout: fixed;word-wrap:break-word;border-collapse: collapse; width: 100%; border: 1px solid #ddd;font-size: 18px; }th, td {text-align: left;padding: 12px;}tr {border-bottom: 1px solid #ddd; }tr.header, tr:hover {background-color: #f1f1f1;}.section {margin: 7px auto;box-shadow: 0 2px 3px rgba( 0, 0, 0, .4 );overflow: hidden;-webkit-transition: .35s;transition: .35s;}button.accordion {background: #00C37E;color: #228;  cursor: pointer;padding: 18px;width: 100%;border: none;text-align: left;outline: none;font-size: 15px;transition: 0.4s;}button.accordion.active, button.accordion:hover {background-color: #f19790;}button.accordion:after {content: \"\\002B\";color: #777;font-weight: bold;float: right;margin-left: 5px;}button.accordion.active:after {content: \"\\2212\";}div.panel {padding: 2 6px;background-color: rgba(182, 216, 209, 0.74);max-height: 4px;overflow: hidden;transition: max-height 0.2s ease-out;}";
const String html_css_3 = ".basic-grey {margin-left: auto;margin-right:auto;max-width: 500px;background: #F7F7F7;padding: 25px 15px 25px 10px;font: 12px Georgia, \"Times New Roman\", Times, serif;color: #888;text-shadow: 1px 1px 1px #FFF;border:1px solid #E4E4E4;}.basic-grey h2 {font-size: 25px;padding: 0px 0px 10px 40px;display: block;border-bottom:1px solid #E4E4E4;margin: -10px -15px 30px -10px;;color: #888;}.basic-grey h2>span {display: block;font-size: 8px;}.basic-grey label {display: block;margin: 0px;}.basic-grey label>span {float: left;width: 20%;text-align: right;padding-right: 10px;margin-top: 10px;color: #888;}.basic-grey input[type=\"text\"], .basic-grey input[type=\"email\"],.basic-grey input[type=\"password\"], .basic-grey textarea {border: 1px solid #DADADA;color: #888;height: 30px;margin-bottom: 16px;margin-right: 6px;margin-top: 2px;outline: 0 none;padding: 3px 3px 3px 5px;width: 70%;font-size: 12px;line-height:15px;box-shadow: inset 0px 1px 4px #ECECEC;-moz-box-shadow: inset 0px 1px 4px #ECECEC;-webkit-box-shadow: inset 0px 1px 4px #ECECEC;}.basic-grey textarea{padding: 5px 3px 3px 5px;height:100px;}.basic-grey .button {background: #E27575;border: none;padding: 10px 25px 10px 25px;color: #FFF;box-shadow: 1px 1px 5px #B6B6B6;border-radius: 3px;text-shadow: 1px 1px 1px #9E3F3F;cursor: pointer;}</style>";
const String html_js = "<script type=\"text/javascript\">function fold() {var acc = document.getElementsByClassName(\"accordion\");var i;for (i = 0; i < acc.length; i++) {acc[i].onclick = function() {this.classList.toggle(\"active\");var panel = this.nextElementSibling;if (panel.style.maxHeight){panel.style.maxHeight = null;} else {panel.style.maxHeight = panel.scrollHeight + \"px\";} }}}</script><script type=\"text/javascript\">function search() {var input, filter, table, tr, td, i;input = document.getElementById(\"myInput\");filter = input.value.toUpperCase();table = document.getElementById(\"myTable\");tr = table.getElementsByTagName(\"div\");for (i = 0; i < tr.length; i++) {td = tr[i].getElementsByTagName(\"button\")[0];if (td) {if (td.innerHTML.toUpperCase().indexOf(filter) > -1) {tr[i].style.display = \"\";} else {tr[i].style.display = \"none\";}} }}</script>";
const String html_top = "<body onload=\"fold()\"><h2>密码本</h2><input type=\"text\" id=\"myInput\" onkeyup=\"search()\" placeholder=\"搜索站点...\"><div id=\"myTable\">";
const String html_new = "<div class = \"section\"><button class=\"accordion\"> ●&nbsp;新增密码信息</button><div class=\"panel\"><form method=\"post\" enctype=\"application/x-www-form-urlencoded\" action=\"/\" class=\"basic-grey\"><h2>Password Form<span>Please fill all the texts in the fields.</span></h2><label><span>网址站点*:</span><input type=\"hidden\"  name=\"command\" value=\"1\"><input type=\"text\" name=\"SITE\" placeholder=\"Bilibili\" required=\"\"></label><label><span>用户姓名*:</span><input type=\"text\" name=\"ID\" placeholder=\"爱吃花椒的喵酱\" required=\"\"></label><label> <span>账号密码*:</span><input type=\"password\" name=\"PWD\" placeholder=\"wbb1997bilibli\" required=\"\"></label><label><span>注册电话:</span><input type=\"text\" name=\"PHONE\" placeholder=\"13000000000\" ></label><label><span>注册邮箱:</span><input type=\"email\" name=\"EMAIL\" placeholder=\"100000@qq.com\" ></label><label><span>关联信息:</span><input type=\"text\" name=\"LINK\" placeholder=\"绑定微信\" ></label><label><span>备注信息:</span><textarea name=\"TIPS\" placeholder=\"这是大号\" ></textarea></label><label><span>&nbsp;</span><input type=\"submit\" class=\"button\" value=\"Submit\"></label></form>  </div></div>";
const String html_confirm_1 = "<div class = \"section\"><button class=\"accordion\"> ●&nbsp;修改系统消息</button><div class=\"panel\"><form method=\"post\" enctype=\"application/x-www-form-urlencoded\" action=\"/\" class=\"basic-grey\"><h2>Set System Info<span>Please fill your original password in the fields.</span></h2><label><span>登陆密码:</span><input type=\"hidden\"  name=\"command\" value=\"4\"><input type=\"password\" name=\"WWW_PWD\" placeholder=\"你的登录密码\" required=\"\"></label><label><span>&nbsp;</span><input type=\"submit\" class=\"button\" value=\"Submit\"></label></form> ";
const String html_forget_1 = "<form method=\"post\" enctype=\"application/x-www-form-urlencoded\" action=\"/\" class=\"basic-grey\"><h2>Great!!!<span>You, lost password again! </span><br><span>点击找回密码后五秒钟内按物理按键</span><br><span>提示：长按按键后再通电开机可重置登陆用户密码，长点记性</span><br></h2><label><input type=\"hidden\"  name=\"command\" value=\"5\"></label><label><span>&nbsp;</span><input type=\"submit\" class=\"button\" value=\"忘记登陆密码\"></label></form>";
const String html_forget_2 = "<form method=\"post\" enctype=\"application/x-www-form-urlencoded\" action=\"/\" class=\"basic-grey\"><h2>Great!!!<span>You, lost password again! </span><br><span>没有办法咯，不断重复试一试你的常用的6位数字密码</span><br><span>实在不行只能找密码本制作人重置密码了</span><br></h2></form>";
const String html_confirm_2 = "</div></div>";

const String html_setting_1 = "<div class = \"section\"><button class=\"accordion\">修改系统消息</button><div class=\"panel\"><form method=\"post\" enctype=\"application/x-www-form-urlencoded\" action=\"/\" class=\"basic-grey\"><h2>Modify System Setting Form<span>Please fill all the texts in the fields.</span></h2><label><span>WIFI名称*:</span><input type=\"hidden\"  name=\"command\" value=\"0\"><input type=\"text\" name=\"WIFI_ID\" placeholder=\"";
const String html_setting_1_init = "<div class = \"section\"><button class=\"accordion\">嗨~别来无恙</button><div class=\"panel\"><form method=\"post\" enctype=\"application/x-www-form-urlencoded\" action=\"/\" class=\"basic-grey\"><h2>Hello~ Welcome to Password Book<span>Please initialize all of the following information</span></h2><label><span>WIFI名称*:</span><input type=\"hidden\"  name=\"command\" value=\"0\"><input type=\"text\" name=\"WIFI_ID\" placeholder=\"";
const String html_setting_2 = "\" required=\"\"></label><label><span>WIFI密码*:</span><input type=\"password\" name=\"WIFI_PWD\" placeholder=\"";
const String html_setting_3 = "\" required=\"\"></label><label><span>登陆名称*:</span><input type=\"text\" name=\"WWW_ID\" placeholder=\"";
const String html_setting_4 = "\" required=\"\"></label><label><span>登陆密码*:</span><input type=\"password\" name=\"WWW_PWD\" placeholder=\"";
const String html_setting_5 = "\" required=\"\"></label><label><span>&nbsp;</span><input type=\"submit\" class=\"button\" value=\"Submit\"></label></form>  </div></div>";
const String html_recyc = "<div class = \"section\"> <form action=\"/\" method=\"post\"><div><input type=\"hidden\"  name=\"command\" value=\"3\"><button class=\"accordion\"> ●&nbsp;密码回收站</button></div></form></div>";
const String html_rt = "<div class = \"section\"><a href=\"/\"><button class=\"accordion\"> ●&nbsp;返回密码列表</button><a></div> ";
const String html_section_1 = "<div class = \"section\"><button class=\"accordion\">";
const String html_section_2 = "</button><div class=\"panel\"><table border = \"1\"><tr ><th width=\"30%\" bgcolor=\"#ccccd6\">站点</th><th width=\"70%\" bgcolor=\"#c3d7df\">";
const String html_section_3 = "</th></tr><tr><td bgcolor=\"#ccccd6\">用户</td><td bgcolor=\"#c3d7df\">";
const String html_section_4 = "</td></tr><tr><td bgcolor=\"#ccccd6\">密码</td><td bgcolor=\"#c3d7df\">";
const String html_section_5 = "</td></tr><tr><td bgcolor=\"#ccccd6\">手机</td><td bgcolor=\"#c3d7df\">";
const String html_section_6 = "</td></tr><tr><td bgcolor=\"#ccccd6\">邮箱</td><td bgcolor=\"#c3d7df\">";
const String html_section_7 = "</td></tr><tr><td bgcolor=\"#ccccd6\">关联</td><td bgcolor=\"#c3d7df\">";
const String html_section_8 = "</td></tr><tr><td bgcolor=\"#ccccd6\">备注</td><td bgcolor=\"#c3d7df\">";  
const String html_section_9 = "</td></tr></table>";
const String html_del_1 = "<form action=\"/\" method=\"post\"><div><input type=\"hidden\"  name=\"command\" value=\"2\"><input type=\"hidden\"  name=\"id\" value=\"";
const String html_del_2 = "\"><input type=\"submit\" class=\"button\" value=\"删除本条密码信息\"></div></form>";
const String html_section_end = "</div></div>";

const String html_about = "<div class = \"section\"><button class=\"accordion\">●&nbsp;About关于</button><div class=\"panel\"><h3>关于本站点：</h3><p>程序版本：v1.3<br>作者：skyqin 由knyin修改 <br>oshwhub：<a href=\"https://baidu.com\">ESP8266_PASSWORDBOOK<a><br></p></div></div>";
const String html_tail = "</div></body></html>";

// ===================== ZW101指纹示例 =====================
// 定义指令包格式
const uint8_t HEADER_HIGH = 0xEF;
const uint8_t HEADER_LOW = 0x01;
const uint32_t DEVICE_ADDRESS = 0xFFFFFFFF;

// 定义指令码
const uint8_t CMD_GET_IMAGE = 0x01;  // 获取图像
const uint8_t CMD_GEN_CHAR = 0x02;   // 生成特征
const uint8_t CMD_MATCH = 0x03;   // 精确比对指纹
const uint8_t CMD_SEARCH = 0x04;   // 搜索指纹
const uint8_t CMD_REG_MODEL = 0x05;  // 合并特征
const uint8_t CMD_STORE_CHAR = 0x06; // 存储模板
const uint8_t CMD_CLEAR_LIB = 0x0D;   // 清空指纹库
const uint8_t CMD_READ_SYSPARA = 0x0F; // 读模组基本参数
const uint8_t CMD_HAND_SHAKE = 0x35; // 握手指令，检测模组是否正常工作
const uint8_t CMD_Read_Num = 0x1d; // 读有效模板个数
const uint8_t CMD_Read_AddPara = 0x62; // 删除N个模板


// 定义缓冲区ID
uint8_t BUFFER_ID = 0;

// 定义模板存储位置 
uint16_t TEMPLATE_ID = 0;

bool isyz = false;
bool yzfstlst = false;
unsigned long lastDisplayOff = 0;

int validNum = 0;
int touch_status = 0;

// 函数声明
int read_FP_info(void);
void send_cmd(uint8_t cmd);
void send_cmd2(uint8_t cmd, uint8_t param1 );
void sendCommand(uint8_t cmd, uint8_t param1 = 0, uint16_t param2 = 0);
void sendCommand1(uint8_t cmd, uint8_t param1, uint16_t param2, uint16_t param3);
bool receiveResponse();
void printResponse(uint8_t *response, uint8_t length);
int validTempleteNum(void);
int read_add_para(void);
int search_FP(void);
int addFinger(int num);

void handleNotFound();
void handleRoot();
void turn_on_light();
void turn_off_light();
void load_info();
void oled_on();
void oled_off();
void finger_open();
void finger_close();
int read_finger_touch(void);

void oled_on(){
  digitalWrite(OLEDCtrl , HIGH);
}

void oled_off(){
  digitalWrite(OLEDCtrl , LOW);
}

void finger_open(){
  digitalWrite(fingerCtrl , HIGH);
}

void finger_close(){
  digitalWrite(fingerCtrl , LOW);
}

int read_finger_touch(){
    return digitalRead(touchCtrl);
}

void printHex(uint8_t* data, uint8_t len) {
  for(uint8_t i=0; i<len; i++){
    if(data[i] < 0x10) Serial.print('0');
    Serial.print(data[i], HEX);
    Serial.print(' ');
  }
  Serial.println();
}

// ===================== ZW101指纹模块核心函数 =====================
// 1. 发送指令到ZW101并获取响应（基础通信函数）
//读模组基本参数
int read_FP_info(void)
{
  uint8_t response[32];
  uint8_t index = 0;
  uint32_t startTime = millis();
  
  Serial.println("ZW101 FPM info:");
  send_cmd(CMD_READ_SYSPARA);
  
  // 等待响应包
  while (millis() - startTime < 2000) {
    if (fingerprintSerial.available()) {
      response[index++] = fingerprintSerial.read();
      if (index >= 28) break;
    }
  }

  // 打印响应包
  // printResponse(response, index);

  // 检查确认码
  if (index >= 28 && response[9] == 0x00) {
    u16 register_cnt = (u16)(response[10]<<8) | response[11];
    u16 fp_temp_size = (u16)(response[12]<<8) | response[13];
    u16 fp_lib_size  = (u16)(response[14]<<8) | response[15];
    u16 score_level  = (u16)(response[16]<<8) | response[17];
    u32 device_addr  = (u32)(response[18]<<24) | (response[19]<<16)| (response[20]<<8) | response[21];
    u16 data_pack_size = (u16)(response[22]<<8) | response[23];
    if(0 == data_pack_size) {
      data_pack_size = 32;
    }
    else if(1 == data_pack_size) {
      data_pack_size = 64;
    }
    else if(2 == data_pack_size) {
      data_pack_size = 128;
    }
    else if(3 == data_pack_size) {
      data_pack_size = 256;
    }
    u16 baud_set = (u16)(response[24]<<8) | response[25];
    
    Serial.print("register cnt:");
    Serial.println(register_cnt);
    Serial.print("temp size:0x");
    Serial.println(fp_temp_size,HEX);
    Serial.print("lib size:");
    Serial.println(fp_lib_size);
    Serial.print("level:");
    Serial.println(score_level);
    Serial.print("devece address:0x");
    Serial.println(device_addr,HEX);
    Serial.print("data size:");
    Serial.println(data_pack_size);
    Serial.print("baud:");
    Serial.println(baud_set*9600);
    Serial.println("ZW101 init successed");
    return (register_cnt);  // 成功
  } else {
    Serial.println("ZW101 init failed, status: ");
    return 0; // 失败
  }
  
}

// 2. 初始化ZW101模块
//检测模组是否正常工作
int fingerInit(void)
{
  
  delay(200);
  send_cmd(CMD_HAND_SHAKE);
  // 等待指纹模组响应
    if (receiveResponse()) {
      
      return true;
    } else {
      
      return false;
    }
  
}

// 3. 读取有效模板个数
int validTempleteNum(void)
{
  
  send_cmd(CMD_Read_Num);
  uint8_t response[50];
  uint8_t index = 0;
  uint32_t startTime = millis();
  
  // 等待响应包
  while (millis() - startTime < 500) {
    if (fingerprintSerial.available()) {
      response[index++] = fingerprintSerial.read();
    }
  }

  // 打印响应包
#if defined(HLK_DEBUG)
  printResponse(response, index);
#endif

  // 检查确认码
  if (index >= 12 && response[9] == 0x00) {
    u16 result = (u16)(response[10]<<8) | response[11];
    Serial.print("有效指纹模板个数：");
    Serial.println(result);
    return (result);  // 成功
  }else {
    return (0);
  } 
  
}

//读取模组附加参数CMD_Read_AddPara
int read_add_para(void)
{
  
  send_cmd(CMD_Read_AddPara);
  uint8_t response[50];
  uint8_t index = 0;
  uint32_t startTime = millis();
  
  // 等待响应包
  while (millis() - startTime < 500) {
    if (fingerprintSerial.available()) {
      response[index++] = fingerprintSerial.read();
    }
  }

  // 打印响应包
#if defined(HLK_DEBUG)
  printResponse(response, index);
#endif

  // 检查确认码
  if (index >= 12 && response[9] == 0x00) {
    Serial.print("模组附加参数：");
    Serial.print((response[10]<<8) | response[11],HEX);
    Serial.print(' ');
    Serial.print((response[12]<<8) | response[13],HEX);
    Serial.print(' ');
    Serial.print((response[14]<<8) | response[15],HEX);
    Serial.print(' ');
    Serial.println((response[16]<<8) | response[17],HEX);
    return (1);  // 成功
  }else {
    return (0);
  } 
  
}

// 6. 删除所有指纹（返回状态：0=成功，1=失败）
//清空指纹库
int clear_FP_all_lib(void)
{
  
  send_cmd(CMD_CLEAR_LIB);
  if (receiveResponse()) {
    return 1;
  }
  return 0;
  
}

// ===================== ZW101指纹模块发送指令函数 =====================
// 发送指令包send_cmd
void send_cmd(uint8_t cmd) {
  uint8_t packet[12];
  uint16_t length=3;
  uint16_t checksum =  1+length+cmd;

  // 构建指令包
  packet[0] = HEADER_HIGH;
  packet[1] = HEADER_LOW;
  packet[2] = (DEVICE_ADDRESS >> 24) & 0xFF;
  packet[3] = (DEVICE_ADDRESS >> 16) & 0xFF;
  packet[4] = (DEVICE_ADDRESS >> 8) & 0xFF;
  packet[5] = DEVICE_ADDRESS & 0xFF;
  packet[6] = 0x01;  // 包标识：命令包
  packet[7] = (length >> 8) & 0xFF;  // 包长度高字节
  packet[8] = length & 0xFF;  // 包长度低字节
  packet[9] = cmd;
  
  packet[10] = (checksum >> 8) & 0xFF;
  packet[11] = checksum & 0xFF;

#if defined(HLK_DEBUG)
  Serial.println("send1:");
  printHex(packet,(2+4+3+length));
#endif

  // 发送指令包
  for (int i = 0; i < (2+4+3+length); i++) {
    fingerprintSerial.write(packet[i]);
  }
  
}
// 发送指令包send_cmd2
void send_cmd2(uint8_t cmd, uint8_t param1 ) {
  uint8_t packet[13];
  uint16_t length=4;
  uint16_t checksum =  1+length+cmd + param1;

  // 构建指令包
  packet[0] = HEADER_HIGH;
  packet[1] = HEADER_LOW;
  packet[2] = (DEVICE_ADDRESS >> 24) & 0xFF;
  packet[3] = (DEVICE_ADDRESS >> 16) & 0xFF;
  packet[4] = (DEVICE_ADDRESS >> 8) & 0xFF;
  packet[5] = DEVICE_ADDRESS & 0xFF;
  packet[6] = 0x01;  // 包标识：命令包
  packet[7] = (length >> 8) & 0xFF;  // 包长度高字节
  packet[8] = length & 0xFF;  // 包长度低字节
  packet[9] = cmd;
  packet[10] = param1;
  packet[11] = (checksum >> 8) & 0xFF;
  packet[12] = checksum & 0xFF;

#if defined(HLK_DEBUG)
  Serial.println("send2:");
  printHex(packet,(2+4+3+length));
#endif

  // 发送指令包
  for (int i = 0; i < (2+4+3+length); i++) {
    fingerprintSerial.write(packet[i]);
  }

}

// 发送指令包sendCommand
void sendCommand(uint8_t cmd, uint8_t param1, uint16_t param2) {
  uint8_t packet[15];
  uint16_t length=6;
  uint16_t checksum =  1+length+cmd + param1 + (param2 >> 8) + (param2 & 0xFF);

  // 构建指令包
  packet[0] = HEADER_HIGH;
  packet[1] = HEADER_LOW;
  packet[2] = (DEVICE_ADDRESS >> 24) & 0xFF;
  packet[3] = (DEVICE_ADDRESS >> 16) & 0xFF;
  packet[4] = (DEVICE_ADDRESS >> 8) & 0xFF;
  packet[5] = DEVICE_ADDRESS & 0xFF;
  packet[6] = 0x01;  // 包标识：命令包
  packet[7] = (length >> 8) & 0xFF;  // 包长度高字节
  packet[8] = length & 0xFF;  // 包长度低字节
  packet[9] = cmd;
  packet[10] = param1;
  packet[11] = (param2 >> 8) & 0xFF;
  packet[12] = param2 & 0xFF;
  packet[13] = (checksum >> 8) & 0xFF;
  packet[14] = checksum & 0xFF;

#if defined(HLK_DEBUG)
  Serial.println("send:");
  printHex(packet,(2+4+3+length));
#endif

  // 发送指令包
  for (int i = 0; i < (2+4+3+length); i++) {
    fingerprintSerial.write(packet[i]);
  }
}

// 发送指令包sendCommand1
void sendCommand1(uint8_t cmd, uint8_t param1, uint16_t param2, uint16_t param3) {
  uint8_t packet[17];
  uint16_t length=8;
  uint16_t checksum =  1+length+cmd + param1 + (param2 >> 8) + (param2 & 0xFF) + (param3 >> 8) + (param3 & 0xFF);

  // 构建指令包
  packet[0] = HEADER_HIGH;
  packet[1] = HEADER_LOW;
  packet[2] = (DEVICE_ADDRESS >> 24) & 0xFF;
  packet[3] = (DEVICE_ADDRESS >> 16) & 0xFF;
  packet[4] = (DEVICE_ADDRESS >> 8) & 0xFF;
  packet[5] = DEVICE_ADDRESS & 0xFF;
  packet[6] = 0x01;  // 包标识：命令包
  packet[7] = (length >> 8) & 0xFF;  // 包长度高字节
  packet[8] = length & 0xFF;  // 包长度低字节
  packet[9] = cmd;
  packet[10] = param1;
  packet[11] = (param2 >> 8) & 0xFF;
  packet[12] = param2 & 0xFF;
  packet[13] = (param3 >> 8) & 0xFF;
  packet[14] = param3 & 0xFF;
  packet[15] = (checksum >> 8) & 0xFF;
  packet[16] = checksum & 0xFF;

#if defined(HLK_DEBUG)
  Serial.println("send:");
  printHex(packet,(2+4+3+length));
#endif

  // 发送指令包
  for (int i = 0; i < (2+4+3+length); i++) {
    fingerprintSerial.write(packet[i]);
  }
}

// 接收响应包
bool receiveResponse() {
  uint8_t response[50];
  uint8_t index = 0;
  uint32_t startTime = millis();

  // 等待响应包
  while (millis() - startTime < 500) {
    if (fingerprintSerial.available()) {
      response[index++] = fingerprintSerial.read();
    }
  }

  // 打印响应包
#if defined(HLK_DEBUG)
  printResponse(response, index);
#endif

  // 检查确认码
  if (index >= 12 && response[9] == 0x00) {
    return true;  // 成功
  } else {
    return false; // 失败
  }
}

// 打印响应包
void printResponse(uint8_t *response, uint8_t length) {
  Serial.print("Response:");
  for (int i = 0; i < length; i++) {
    if(response[i] < 0x10) Serial.print('0');
    Serial.print(response[i], HEX);
    Serial.print(" ");
  }
  Serial.println();
}


// ===================== 初始化（新增指纹模块初始化） =====================
void setup(void) {
  //pass配置
  #if VERSION == 0
  pinMode(BUTTON, INPUT);
  pinMode(LIGHT_1, OUTPUT);
  digitalWrite(LIGHT_1, 1);
  pinMode(LIGHT_2, OUTPUT);
  #elif VERSION == 1
  pinMode(BUTTON, INPUT);
  #endif

  Serial.begin(115200);
  pinMode(fingerCtrl , OUTPUT);
  pinMode(touchCtrl , INPUT);
  pinMode(OLEDCtrl, OUTPUT);
  finger_open();
  oled_on();
  u8g2.begin();
  u8g2.enableUTF8Print();
  u8g2.setFont(u8g2_font_wqy14_t_gb2312);  // use chinese2 for all the glyphs of "你好世界"
  u8g2.setFontDirection(0);
  for (uint8_t t = 3; t > 0; t--) {
    Serial.printf("[SETUP] WAIT %d...\n", t);
    Serial.flush();
    u8g2.firstPage();
    do{
    u8g2.setCursor(4, 32);
    u8g2.print("---准 备 开 始---");
    u8g2.setCursor(63, 52);
    u8g2.print(t-1);
    }while ( u8g2.nextPage() );
    delay(1000);
  }

  // 初始化指纹模块（提前初始化，确保后续检查有效）
  fingerprintSerial.begin(57600);
  
  if (!fingerInit()){
    Serial.println("指纹模块检测失败");

    u8g2.firstPage();
    do{
    u8g2.setCursor(0, 24);
    u8g2.print("指纹模块初始化失败");
    u8g2.setCursor(14, 48);
    u8g2.print("请检查是否安装");
    }while ( u8g2.nextPage() );
    //指纹未注册进入注册模块
    while(1){};
  }
  
  //read_FP_info();

  //如果指纹数量为0，则先进入添加指纹的步骤
  validNum = validTempleteNum();
  if (validNum ==0){
    addFinger(validNum);
  }
  
  //read_add_para();
  
}
  
void loop(void){
  if (!isyz)
  { //未验证指纹，则先验证才能继续使用
    u8g2.firstPage();
    do{
    u8g2.setCursor(7, 24);
    u8g2.print("请按指纹进行验证");
    u8g2.setCursor(21, 48);
    u8g2.print("按住不要放开");
    }while ( u8g2.nextPage() );
    delay(1000);

    if(read_finger_touch()){
      
      if (search_FP()){
        Serial.println("找到指纹");
        u8g2.firstPage();
        do{
          u8g2.setCursor(7, 24);
          u8g2.print("已匹配指纹库指纹");
          u8g2.setCursor(21, 48);
          u8g2.print("正在启动系统");
          }while ( u8g2.nextPage() );
        delay(1000);

        isyz = true;

        load_info();
        if(digitalRead(BUTTON) == 0){//mine is default high
          Serial.println("开机重置密码");
          strcpy(sys_info.www_pwd , "123456");
          save_sys(sys_info);
        }
        Serial.print("Setting soft-AP ... ");
  
        IPAddress softLocal(192,168,4,1);   
        IPAddress softGateway(192,168,4,1);
        IPAddress softSubnet(255,255,255,0);
  
        WiFi.softAPConfig(softLocal, softGateway, softSubnet);  
 
        WiFi.softAP(sys_info.wifi_ssid, sys_info.wifi_pwd, 2, 0, 1);
  
        IPAddress myIP = WiFi.softAPIP();
        Serial.print("AP IP address: ");
        Serial.println(myIP);
  
        pinMode(led, OUTPUT);
        turn_off_light();

        if (MDNS.begin("esp8266")) {
          Serial.println("MDNS responder started");
        }

        server.on("/", handleRoot);
  
        server.onNotFound(handleNotFound);

        server.begin();
        Serial.println("HTTP server started");

        u8g2.firstPage();
        do{
          u8g2.setCursor(21, 24);
          u8g2.print("指纹验证成功");
          u8g2.setCursor(7, 48);
          u8g2.print("请链接WIFI使用");
        }while ( u8g2.nextPage() );
        lastDisplayOff = millis();
        finger_close();

      }
    }
    
    
  }else
  { // 指纹已验证可以使用
    //Serial.println("允许使用");
    
    //验证成功后运行一次
    if (!yzfstlst){
      
      yzfstlst = true;
    }
    //oled显示5秒后关闭电源
    if (millis()-lastDisplayOff == 5000){
      oled_off();
    }
          
    server.handleClient();
  }

}


//搜索指纹
int search_FP(void)
{
  
  int serch_cnt = 0; 
  BUFFER_ID = 1;
  while(serch_cnt <= 5) {
    // 步骤1：获取图像
    send_cmd(CMD_GET_IMAGE);
  
    // 等待指纹模组响应
    if (receiveResponse()) {

    } else {
      delay(1000);
      continue;
    }
  
    // 步骤2：生成特征
    send_cmd2(CMD_GEN_CHAR, BUFFER_ID);
    if (receiveResponse()) {
      break;
    } else {
      serch_cnt++;
      delay(500);
      continue;
    }
  }

  // 步骤3：搜索指纹
  BUFFER_ID = 1;
  sendCommand1(CMD_SEARCH, BUFFER_ID, 1, 10);
  if (receiveResponse()) {
    return 1;
  }
  return 0;
  
}

int addFinger(int num)
{
  TEMPLATE_ID = num;
  touch_status = 1;
  BUFFER_ID = 1;
  while(BUFFER_ID <= 5) {
    // 步骤1：获取图像
    
    Serial.print("获取指纹图像5次，第");
    Serial.print(BUFFER_ID);
    Serial.println("次");
    Serial.print("请按下指纹,不要放开！");

    u8g2.firstPage();
    do{
      u8g2.setCursor(10, 17);
      u8g2.print("获取指纹图像5次");
      u8g2.setCursor(31, 38);
      u8g2.print("第  "+ String(BUFFER_ID)+"  次");
      u8g2.setCursor(7, 60);
      u8g2.print("请按手指,不要放开");
    }while ( u8g2.nextPage() );
    
    
    if (read_finger_touch()){
      if (touch_status){
        send_cmd(CMD_GET_IMAGE);
        touch_status = 0;
      }
      

    } 
    
    // 等待指纹模组响应
    if (receiveResponse()) {
      
      Serial.println("请放开");

      u8g2.firstPage();
    do{
      u8g2.setCursor(28, 38);
      u8g2.print("请放开手指");
    }while ( u8g2.nextPage() );
    
      
    } else {
      if (!read_finger_touch()) touch_status = 1;
      delay(1000);
      
      continue;
    }
        
    // 步骤2：生成特征
    Serial.println("生成特征中");
    send_cmd2(CMD_GEN_CHAR, BUFFER_ID);
    if (receiveResponse()) {
      BUFFER_ID++;
    } else {
      continue;
    }
    
  }

  // 步骤3：合并特征
  Serial.println("合并特征");
  send_cmd(CMD_REG_MODEL);
  if (receiveResponse()) {
    Serial.println("合并指纹特征成功！");
  } else {
    Serial.println("合并指纹特征失败！");
    return 0;
  }

  // 步骤4：存储模板
  Serial.print("保存指纹到");
  Serial.println(TEMPLATE_ID);
  BUFFER_ID = 1;
  sendCommand(CMD_STORE_CHAR, BUFFER_ID, TEMPLATE_ID);
  if (receiveResponse()) {
    
  } else {
    return 0;
  }

  u8g2.firstPage();
    do{
      u8g2.setCursor(21, 38);
      u8g2.print("指纹保存成功");
    }while ( u8g2.nextPage() );
    delay(1000);

  return 1;
  
}

//pass配置
void load_info(){
  flash_init(sys_info, pwd_info);
  
  Serial.println("----------- SYS_INFO -----------");
  Serial.println(sys_info.sys_state);
  Serial.println(sys_info.pwd_num);
  Serial.println(sys_info.wifi_ssid);
  Serial.println(sys_info.wifi_pwd);
  Serial.println(sys_info.www_id);
  Serial.println(sys_info.www_pwd);
  Serial.println();

  Serial.println("----------- PWD_INFO -----------");
  Serial.println(pwd_info.site);
  Serial.println(pwd_info.id);
  Serial.println(pwd_info.pwd);
  Serial.println(pwd_info.phone);
  Serial.println(pwd_info.email);
  Serial.println(pwd_info.link);
  Serial.println(pwd_info.tips);
}

void turn_on_light(){
  digitalWrite(led, 0);
  #if VERSION == 0
  digitalWrite(LIGHT_1, 0);
  digitalWrite(LIGHT_2, 1);
  #endif
}

void turn_off_light(){
  delay(300);
  digitalWrite(led, 1);
  #if VERSION == 0
  digitalWrite(LIGHT_1, 1);
  digitalWrite(LIGHT_2, 0);
  #endif
}

void handleRoot() {
    if(server.method() != HTTP_POST && sys_info.sys_state == 0){//init setting
      Serial.println("----------- 进入初始设置页面 -----------");
      
      turn_on_light();
      String html = html_head + html_css_1 + html_css_2 + html_css_3 + html_js + html_top ;
      html += html_setting_1_init + String(sys_info.wifi_ssid) + html_setting_2 + " (起码8位)"  + html_setting_3 + String(sys_info.www_id) + html_setting_4 + String(sys_info.www_pwd) + "(6位数,勿忘！)" + html_setting_5 + html_about + html_tail;
      server.send(200, "text/html", html);
      turn_off_light();
    }
    else {//normal mode
      Serial.println("----------- NORMAL MODE -----------");
      if(sys_info.sys_state > 0){
        if (!server.authenticate(sys_info.www_id, sys_info.www_pwd))
        return server.requestAuthentication();
      }else{
        sys_info.sys_state++;
        save_sys(sys_info);
      }
      

      if (server.method() != HTTP_POST) {
        Serial.println("----------- 正常显示 -----------");
        turn_on_light();
        String html = html_head + html_css_1 + html_css_2 + html_css_3 + html_js + html_top + html_confirm_1;
        #if VERSION == 2
          html += html_forget_2;
        #else
          html += html_forget_1;
        #endif

        html += html_confirm_2 + html_new + html_recyc;
        int index = 0;
        for(int i = 0; i < sys_info.pwd_num; i++){
          if(sys_info.show_flag[i]){
            index++;
            load_pwd(i, pwd_info); 
            html += html_section_1 + String(index) + ". " + String(pwd_info.site) + html_section_2 + String(pwd_info.site) + html_section_3 + String(pwd_info.id) + html_section_4 + String(pwd_info.pwd) + html_section_5 + String(pwd_info.phone) + html_section_6 + String(pwd_info.email) + html_section_7 + String(pwd_info.link)+ html_section_8 + String(pwd_info.tips) + html_section_9 + html_del_1;
            html += String(i) + html_del_2 + html_section_end;
          }
        }
        html += html_about + html_tail;
        server.send(200, "text/html", html);
        turn_off_light();
      }else{
        //对于第一个POST参数server.arg(0)
        //0. 修改系统消息 1. 新增 2. 删除  3. 进入回收站页面 4. 进入确认原始密码页面 5. 进入修改设置页面
        if(server.arg(0) == "0"){
          Serial.println("----------- 处理修改设置 -----------");
          turn_on_light();
          strcpy(sys_info.www_id , server.arg(3).c_str());
          strcpy(sys_info.www_pwd , server.arg(4).c_str());
          if(sys_info.sys_state == 0)
            sys_info.sys_state = 1;
          if(strcmp(sys_info.wifi_ssid, server.arg(1).c_str()) || strcmp(sys_info.wifi_pwd, server.arg(2).c_str())){
            strcpy(sys_info.wifi_ssid , server.arg(1).c_str());
            strcpy(sys_info.wifi_pwd , server.arg(2).c_str());
            save_sys(sys_info);
            String plain = String("WiFi information has been modified\nPlease connect to the new wifi\n\twifi_ssid: ")+ String(sys_info.wifi_ssid)+ String("\n\twifi_password: ") + String(sys_info.wifi_pwd)+ String("\n\tusr_name: ") + String(sys_info.www_id)+ String("\n\tusr_password: ") + String(sys_info.www_pwd);
            server.send(200, "text/plain", plain);
            delay(1000);
            ESP.reset();
          }else{
            save_sys(sys_info);
            server.send(200, "text/html", "<meta http-equiv=\"refresh\" content=\"0;url=/\">");
          }
          turn_off_light();
        }else if(server.arg(0) == "1"){
          Serial.println("----------- 处理新增信息 -----------");
          turn_on_light();
          strcpy(pwd_info.site , server.arg(1).c_str());
          strcpy(pwd_info.id , server.arg(2).c_str());
          strcpy(pwd_info.pwd , server.arg(3).c_str());
          strcpy(pwd_info.phone , server.arg(4).c_str());
          strcpy(pwd_info.email , server.arg(5).c_str());
          strcpy(pwd_info.link , server.arg(6).c_str());
          strcpy(pwd_info.tips , server.arg(7).c_str());

          save_pwd(sys_info.pwd_num, pwd_info);
          sys_info.show_flag[sys_info.pwd_num] = 1;
          sys_info.pwd_num++;
          
          save_sys(sys_info);

          turn_off_light();
          server.send(200, "text/html", "<meta http-equiv=\"refresh\" content=\"0;url=/\">");
          //server.sendHeader("Location","/");
        }else if(server.arg(0) == "2"){
          turn_on_light();
          Serial.println("----------- 处理删除信息 -----------");
          uint16_t n = atoi(server.arg(1).c_str());
          Serial.print("获取的编号：");
          Serial.println(n);
          sys_info.show_flag[n] = 0;
          save_sys(sys_info);
          server.send(200, "text/html", "<meta http-equiv=\"refresh\" content=\"0;url=/\">");
          turn_off_light();
        }else if(server.arg(0) == "3"){
          Serial.println("----------- 进回收站页面 -----------");
          turn_on_light();
          String html = html_head + html_css_1 + html_css_2 + html_css_3 + html_js + html_top + html_rt;
          int index = 0;
          Serial.println("标识情况：");
          Serial.print("    总密码数:");
          Serial.println(sys_info.pwd_num);
          for(int i = 0; i < sys_info.pwd_num; i++){
            Serial.print("    ");
            Serial.print(i);
            Serial.print(" : ");
            Serial.println(sys_info.show_flag[i]);
            if(sys_info.show_flag[i] == 0){
                Serial.print("即将展示：");
                Serial.println(i);
                index++;
                load_pwd(i, pwd_info); 
                html += html_section_1 + String(index) + ". " + String(pwd_info.site) + html_section_2 + String(pwd_info.site) + html_section_3 + String(pwd_info.id) + html_section_4 + String(pwd_info.pwd) + html_section_5 + String(pwd_info.phone) + html_section_6 + String(pwd_info.email) + html_section_7 + String(pwd_info.link)+ html_section_8 + String(pwd_info.tips) + html_section_9 + html_section_end;          
              }
          }
          html +=  html_tail;
          server.send(200, "text/html", html);
          turn_off_light();
        }else if(server.arg(0) == "4"){
            Serial.println("----------- 进入修改设置页面 -----------");
            if(server.arg(1) == sys_info.www_pwd){
              turn_on_light();
              String html = html_head + html_css_1 + html_css_2 + html_css_3 + html_js + html_top ;
              html += html_setting_1 + String(sys_info.wifi_ssid) + html_setting_2 ;
              html += String(sys_info.wifi_pwd) + String(" (起码8位)") + html_setting_3 + String(sys_info.www_id) + html_setting_4 + String(sys_info.www_pwd) + "(6位数,勿忘！)" + html_setting_5 + html_about + html_tail;
              server.send(200, "text/html", html);
              turn_off_light();
            }else{
              server.send(200, "text/html", "<meta http-equiv=\"refresh\" content=\"0;url=/\">");
            }
        }else if(server.arg(0) == "5"){
            Serial.println("---------- 处理重置登录密码 ---------");
            turn_on_light();
            
            unsigned long start = millis();
            while(millis()-start < 5000){
              if((millis()-start)%1000 == 0)
                Serial.println(digitalRead(BUTTON));
              if(digitalRead(BUTTON) == 0){//mine is default high
                Serial.println("按下了按键");
                strcpy(sys_info.www_pwd , "123456");
                save_sys(sys_info);
                
                break;
              }
              
            }
            server.send(200, "text/html", "<meta http-equiv=\"refresh\" content=\"0;url=/\">");
            turn_on_light();
        }

      }
    }
    

}

void handleNotFound() {
  turn_off_light();
  String message = "File Not Found\n\n";
  message += "URI: ";
  message += server.uri();
  message += "\nMethod: ";
  message += (server.method() == HTTP_GET) ? "GET" : "POST";
  message += "\nArguments: ";
  message += server.args();
  message += "\n";
  for (uint8_t i = 0; i < server.args(); i++) {
    message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
  }
  server.send(404, "text/plain", message);
  delay(1000);
  server.send(200, "text/html", "<head><meta http-equiv=\"refresh\" content=\"0;url=\"/\"\"> </head>");
  turn_off_light();
}